﻿/*  ----------------------------------------------------------------------------
 *  DES-Release 2
 *  ----------------------------------------------------------------------------
 *  File:       ServiceQueueBalancer.cs
 *  Author:     nitinkoshy@gmail.com
 *  Source:     http://des.codeplex.com/
 *  ----------------------------------------------------------------------------
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Configuration;
using System.ServiceModel;
using System.Diagnostics;

namespace DES.Server.Service.Workers
{
    //visits the ServerService queue once in a while
    //and if load over threshhold, moves the task to a different server.
    public class LoadBalancer : WorkerBase
    {

        public override void PerformTask(DESServerService serverService)
        {
            if (serverService.CurrentLoad <= serverService.MaxAllowedLoad)
                return;

            //the server is already on a load, hence move the later tasks
            //to other servers
            while (serverService.TaskQueue.Count > serverService.MaxAllowedLoad)
            {
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("ServiceQueueBalancer Message : Current task queue load over max allowed"));
                ServerTask serverTask;

                int leastLoadedServerLoad = 0;
                DESServerInfo leastLoadedServer = LocateLeastLoadedServer(serverService.ConnectedServers, out leastLoadedServerLoad);

                if (leastLoadedServer != null)
                {
                    Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                    Trace.WriteLine(String.Format("ServiceQueueBalancer Message : Located least loaded server : {0}", leastLoadedServer.Id));
                }

                //ignore if the least loaded server is already more loaded than me
                if (leastLoadedServerLoad >= serverService.CurrentLoad)
                {
                    leastLoadedServer = null;
                    Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                    Trace.WriteLine(String.Format("ServiceQueueBalancer Message : least loaded server load > current load. Not Balancing"));
                }

                if (leastLoadedServer == null) //no servers located
                {
                    Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                    Trace.WriteLine(String.Format("ServiceQueueBalancer Message : No possible server located to de-load"));
                    break; //nothing more to check.
                }

                //get the task that we are going to offload
                lock (serverService.TaskQueue)
                {
                    if (serverService.TaskQueue.Count > 0)
                        serverTask = serverService.TaskQueue.Dequeue();
                    else
                        break;//in the possible case of the queue getting empty by the time we reached here
                }

                if (!OffLoadToServer(leastLoadedServer, serverService, serverTask))
                {
                    lock (serverService.TaskQueue)
                    {
                        serverService.TaskQueue.Enqueue(serverTask);//put the task back as we have some error
                    }
                }

            }

        }

        private bool OffLoadToServer(DESServerInfo leastLoadedServer, DESServerService serverService, ServerTask serverTask)
        {
            ChannelFactory<IDESServerService> desServerServiceFactory = new ChannelFactory<IDESServerService>(
                new NetTcpBinding(),
                new EndpointAddress(leastLoadedServer.EndPoint));
            IDESServerService desServerService = desServerServiceFactory.CreateChannel();

            try
            {
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("ServiceQueueBalancer Message : Unloading task to server {0}", leastLoadedServer.Id));
                desServerService.QueueTask(serverTask, serverService.ServerInfo);
                desServerServiceFactory.Close();
            }
            catch (EndpointNotFoundException exception)//this server is not connected
            {
                Trace.Write(String.Format("[{0}/{1}]:", serverService.ServerInfo.Id, DateTime.Now.ToShortTimeString()));
                Trace.WriteLine(String.Format("ServiceQueueBalancer Message : Could not Unload task to server {0} {1}", leastLoadedServer.Id, exception.Message));
                return false;

            }

            return true;
        }

        public override string GetWorkerName()
        {
            return "LoadBalancer";
        }

        public override int GetThreadSleepTime()
        {
            return Convert.ToInt32(ConfigurationManager.AppSettings["balancerthreadsleeptime"]);
        }

        private DESServerInfo LocateLeastLoadedServer(List<DESServerInfo> connectedServers, out int leastLoadedServerLoad)
        {
            DESServerInfo serverInfo = null;
            int serverLoad, minLoad = -1;

            foreach (DESServerInfo server in connectedServers)
            {
                ChannelFactory<IDESServerService> desServerServiceFactory = new ChannelFactory<IDESServerService>(
                    new NetTcpBinding(),
                    new EndpointAddress(server.EndPoint));

                IDESServerService desServerService = desServerServiceFactory.CreateChannel();
                try
                {
                    serverLoad = desServerService.GetServerLoad();
                    desServerServiceFactory.Close();
                }
                catch (EndpointNotFoundException)
                {
                    continue; //this server is not connected. try the next.
                }

                if ((minLoad == -1) || (serverLoad < minLoad))
                {
                    minLoad = serverLoad;
                    serverInfo = server;
                }
                
            }
            leastLoadedServerLoad = minLoad;
            return serverInfo;
        }

    }
}
